#pragma once
#include "MathUtils.h"
#include "Vector.h"
namespace Alice{
    /**
     * Structure for integer vectors in 3-d space.
     */
    template <typename InIntType>
    struct TIntVector3
    {
        using IntType = InIntType;
        static_assert(std::is_integral_v<IntType>, "Only an integer types are supported.");

        union
        {
            struct
            {
                /** Holds the vector's x-coordinate. */
                IntType X;

                /** Holds the vector's y-coordinate. */
                IntType Y;

                /** Holds the vector's z-coordinate. */
                IntType Z;
            };
            IntType XYZ[3];
        };

        /** An int vector with zeroed values. */
        static const TIntVector3 ZeroValue;

        /** An int vector with INDEX_NONE values. */
        static const TIntVector3 NoneValue;

        /**
         * Default constructor (no initialization).
         */
        TIntVector3() = default;

        /**
         * Creates and initializes a new instance with the specified coordinates.
         *
         * @param InX The x-coordinate.
         * @param InY The y-coordinate.
         * @param InZ The z-coordinate.
         */
        TIntVector3(IntType InX, IntType InY, IntType InZ)
            : X(InX)
            , Y(InY)
            , Z(InZ)
        {
        }

        /**
         * Constructor
         *
         * @param InValue replicated to all components
         */
        explicit TIntVector3(IntType InValue)
            : X(InValue)
            , Y(InValue)
            , Z(InValue)
        {
        }

        /**
         * Constructor
         *
         * @param InVector float vector converted to int
         */
        template <typename FloatType>
        explicit TIntVector3(TVector<FloatType> InVector);

        /**
         * Constructor
         *
         * @param EForceInit Force init enum
         */
        explicit TIntVector3(EForceInit)
            : X(0)
            , Y(0)
            , Z(0)
        {
        }

        /**
         * Converts to another int type. Checks that the cast will succeed.
         */
        template <typename OtherIntType>
        explicit TIntVector3(TIntVector3<OtherIntType> Other)
            : X(IntCastChecked<IntType>(Other.X))
            , Y(IntCastChecked<IntType>(Other.Y))
            , Z(IntCastChecked<IntType>(Other.Z))
        {
        }

        // Workaround for clang deprecation warnings for deprecated XYZ member in implicitly-defined special member functions
        TIntVector3(TIntVector3&&) = default;
        TIntVector3(const TIntVector3&) = default;
        TIntVector3& operator=(TIntVector3&&) = default;
        TIntVector3& operator=(const TIntVector3&) = default;

        /**
         * Gets specific component of a vector.
         *
         * @param ComponentIndex Index of vector component.
         * @return const reference to component.
         */
        const IntType& operator()(int32 ComponentIndex) const
        {
    PRAGMA_DISABLE_DEPRECATION_WARNINGS
            return XYZ[ComponentIndex];
    PRAGMA_ENABLE_DEPRECATION_WARNINGS
        }

        /**
         * Gets specific component of a vector.
         *
         * @param ComponentIndex Index of vector component.
         * @return reference to component.
         */
        IntType& operator()(int32 ComponentIndex)
        {
    PRAGMA_DISABLE_DEPRECATION_WARNINGS
            return XYZ[ComponentIndex];
    PRAGMA_ENABLE_DEPRECATION_WARNINGS
        }

        /**
         * Gets specific component of a vector.
         *
         * @param ComponentIndex Index of vector component.
         * @return const reference to component.
         */
        const IntType& operator[](int32 ComponentIndex) const
        {
    PRAGMA_DISABLE_DEPRECATION_WARNINGS
            return XYZ[ComponentIndex];
    PRAGMA_ENABLE_DEPRECATION_WARNINGS
        }

        /**
         * Gets specific component of a vector.
         *
         * @param ComponentIndex Index of vector component.
         * @return reference to component.
         */
        IntType& operator[](int32 ComponentIndex)
        {
    PRAGMA_DISABLE_DEPRECATION_WARNINGS
            return XYZ[ComponentIndex];
    PRAGMA_ENABLE_DEPRECATION_WARNINGS
        }

        /**
         * Compares vectors for equality.
         *
         * @param Other The other int vector being compared.
         * @return true if the vectors are equal, false otherwise..
         */
        bool operator==(const TIntVector3& Other) const
        {
            return X == Other.X && Y == Other.Y && Z == Other.Z;
        }

        /**
         * Compares vectors for inequality.
         *
         * @param Other The other int vector being compared.
         * @return true if the vectors are not equal, false otherwise..
         */
        bool operator!=(const TIntVector3& Other) const
        {
            return X != Other.X || Y != Other.Y || Z != Other.Z;
        }

        /**
         * Multiplies this vector with another vector, using component-wise multiplication.
         *
         * @param Other The vector to multiply with.
         * @return Reference to this vector after multiplication.
         */
        TIntVector3& operator*=(const TIntVector3& Other)
        {
            X *= Other.X;
            Y *= Other.Y;
            Z *= Other.Z;

            return *this;
        }

        /**
         * Scales this vector.
         *
         * @param Scale What to multiply the vector by.
         * @return Reference to this vector after multiplication.
         */
        TIntVector3& operator*=(IntType Scale)
        {
            X *= Scale;
            Y *= Scale;
            Z *= Scale;

            return *this;
        }

        /**
         * Divides this vector.
         *
         * @param Divisor What to divide the vector by.
         * @return Reference to this vector after division.
         */
        TIntVector3& operator/=(IntType Divisor)
        {
            X /= Divisor;
            Y /= Divisor;
            Z /= Divisor;

            return *this;
        }

        /**
         * Remainder of division of this vector.
         *
         * @param Divisor What to divide the vector by.
         * @return Reference to this vector after remainder.
         */
        TIntVector3& operator%=(IntType Divisor)
        {
            X %= Divisor;
            Y %= Divisor;
            Z %= Divisor;

            return *this;
        }

        /**
         * Adds to this vector.
         *
         * @param Other The vector to add to this vector.
         * @return Reference to this vector after addition.
         */
        TIntVector3& operator+=(const TIntVector3& Other)
        {
            X += Other.X;
            Y += Other.Y;
            Z += Other.Z;

            return *this;
        }

        /**
         * Subtracts from this vector.
         *
         * @param Other The vector to subtract from this vector.
         * @return Reference to this vector after subtraction.
         */
        TIntVector3& operator-=(const TIntVector3& Other)
        {
            X -= Other.X;
            Y -= Other.Y;
            Z -= Other.Z;

            return *this;
        }

        /**
         * Gets the result of component-wise multiplication of this vector by another.
         *
         * @param Other The vector to multiply with.
         * @return The result of multiplication.
         */
        TIntVector3 operator*(const TIntVector3& Other) const
        {
            return TIntVector3(*this) *= Other;
        }

        /**
         * Gets the result of scaling on this vector.
         *
         * @param Scale What to multiply the vector by.
         * @return A new scaled int vector.
         */
        TIntVector3 operator*(IntType Scale) const
        {
            return TIntVector3(*this) *= Scale;
        }

        /**
         * Gets the result of division on this vector.
         *
         * @param Divisor What to divide the vector by.
         * @return A new divided int vector.
         */
        TIntVector3 operator/(IntType Divisor) const
        {
            return TIntVector3(*this) /= Divisor;
        }

        /**
         * Gets the remainder of division on this vector.
         *
         * @param Divisor What to divide the vector by.
         * @return A new remainder int vector.
         */
        TIntVector3 operator%(IntType Divisor) const
        {
            return TIntVector3(*this) %= Divisor;
        }

        /**
         * Gets the result of addition on this vector.
         *
         * @param Other The other vector to add to this.
         * @return A new combined int vector.
         */
        TIntVector3 operator+(const TIntVector3& Other) const
        {
            return TIntVector3(*this) += Other;
        }

        /**
         * Gets the result of subtraction from this vector.
         *
         * @param Other The other vector to subtract from this.
         * @return A new subtracted int vector.
         */
        TIntVector3 operator-(const TIntVector3& Other) const
        {
            return TIntVector3(*this) -= Other;
        }

        /**
         * Shifts all components to the right.
         *
         * @param Shift The number of bits to shift.
         * @return A new shifted int vector.
         */
        TIntVector3 operator>>(IntType Shift) const
        {
            return TIntVector3(X >> Shift, Y >> Shift, Z >> Shift);
        }

        /**
         * Shifts all components to the left.
         *
         * @param Shift The number of bits to shift.
         * @return A new shifted int vector.
         */
        TIntVector3 operator<<(IntType Shift) const
        {
            return TIntVector3(X << Shift, Y << Shift, Z << Shift);
        }

        /**
         * Component-wise AND.
         *
         * @param Value Number to AND with the each component.
         * @return A new shifted int vector.
         */
        TIntVector3 operator&(IntType Value) const
        {
            return TIntVector3(X & Value, Y & Value, Z & Value);
        }

        /**
         * Component-wise OR.
         *
         * @param Value Number to OR with the each component.
         * @return A new shifted int vector.
         */
        TIntVector3 operator|(IntType Value) const
        {
            return TIntVector3(X | Value, Y | Value, Z | Value);
        }

        /**
         * Component-wise XOR.
         *
         * @param Value Number to XOR with the each component.
         * @return A new shifted int vector.
         */
        TIntVector3 operator^(IntType Value) const
        {
            return TIntVector3(X ^ Value, Y ^ Value, Z ^ Value);
        }

        /**
         * Is vector equal to zero.
         * @return is zero
        */
        bool IsZero() const
        {
            return *this == ZeroValue;
        }

        /**
         * Gets the maximum value in the vector.
         *
         * @return The maximum value in the vector.
         */
        IntType GetMax() const
        {
            return MathUtils::Max(MathUtils::Max(X, Y), Z);
        }

        /**
         * Get the maximum absolute value in the vector.
         *
         * @return The maximum absolute value in the vector.
         */
        IntType GetAbsMax() const
        {
            if constexpr (std::is_signed_v<IntType>)
            {
                return MathUtils::Max(MathUtils::Max(MathUtils::Abs(X), MathUtils::Abs(Y)), MathUtils::Abs(Z));
            }
            else
            {
                return GetMax();
            }
        }

        /**
         * Gets the minimum value in the vector.
         *
         * @return The minimum value in the vector.
         */
        IntType GetMin() const
        {
            return MathUtils::Min(MathUtils::Min(X, Y), Z);
        }

        /**
         * Get the minimum absolute value in the vector.
         *
         * @return The minimum absolute value in the vector.
         */
        IntType GetAbsMin() const
        {
            if constexpr (std::is_signed_v<IntType>)
            {
                return MathUtils::Min(MathUtils::Min(MathUtils::Abs(X), MathUtils::Abs(Y)), MathUtils::Abs(Z));
            }
            else
            {
                return GetMin();
            }
        }

        /**
         * Get the component-wise max of this vector and the parameter vector.
         * 
         * @param Other The other vector to compare against.
         * @return A vector where each component is the maximum value of the corresponding components of the two vectors.
         */
        TIntVector3 ComponentMax(const TIntVector3& Other) const
        {
            return TIntVector3(
                MathUtils::Max(X, Other.X),
                MathUtils::Max(Y, Other.Y),
                MathUtils::Max(Z, Other.Z));
        }

        /**
         * Get the component-wise min of this vector and the parameter vector.
         * 
         * @param Other The other vector to compare against.
         * @return A vector where each component is the minimum value of the corresponding components of the two vectors.
         */
        TIntVector3 ComponentMin(const TIntVector3& Other) const
        {
            return TIntVector3(
                MathUtils::Min(X, Other.X),
                MathUtils::Min(Y, Other.Y),
                MathUtils::Min(Z, Other.Z));
        }

        /**
         * Gets the distance of this vector from (0,0,0).
         *
         * @return The distance of this vector from (0,0,0).
         */
        IntType Size() const
        {
            int64 LocalX64 = (int64)X;
            int64 LocalY64 = (int64)Y;
            int64 LocalZ64 = (int64)Z;
            return IntType(MathUtils::Sqrt(double(LocalX64 * LocalX64 + LocalY64 * LocalY64 + LocalZ64 * LocalZ64)));
        }

        /**
         * Divide an int vector and round up the result.
         *
         * @param Lhs The int vector being divided.
         * @param Divisor What to divide the int vector by.
         * @return A new divided int vector.
         */
        static TIntVector3 DivideAndRoundUp(TIntVector3 Lhs, IntType Divisor)
        {
            return TIntVector3(MathUtils::DivideAndRoundUp(Lhs.X, Divisor), MathUtils::DivideAndRoundUp(Lhs.Y, Divisor), MathUtils::DivideAndRoundUp(Lhs.Z, Divisor));
        }

        static TIntVector3 DivideAndRoundUp(TIntVector3 Lhs, TIntVector3 Divisor)
        {
            return TIntVector3(MathUtils::DivideAndRoundUp(Lhs.X, Divisor.X), MathUtils::DivideAndRoundUp(Lhs.Y, Divisor.Y), MathUtils::DivideAndRoundUp(Lhs.Z, Divisor.Z));
        }

        /**
         * Gets the number of components a vector has.
         *
         * @return Number of components vector has.
         */
        static int32 Num()
        {
            return 3;
        }
    };
    using IntVector3 = TIntVector3<int32>;
    using IntVector=IntVector3;
}